/**
 * Copyright (c) 2013-Now http://jeesite.com All rights reserved.
 */
package com.kylin.common.core.util;

import com.alibaba.fastjson2.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.apache.hc.client5.http.ConnectTimeoutException;
import org.apache.hc.client5.http.classic.methods.HttpGet;
import org.apache.hc.client5.http.classic.methods.HttpPost;
import org.apache.hc.client5.http.config.RequestConfig;
import org.apache.hc.client5.http.entity.mime.FileBody;
import org.apache.hc.client5.http.entity.mime.MultipartEntityBuilder;
import org.apache.hc.client5.http.entity.mime.StringBody;
import org.apache.hc.client5.http.impl.DefaultHttpRequestRetryStrategy;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder;
import org.apache.hc.client5.http.io.HttpClientConnectionManager;
import org.apache.hc.client5.http.protocol.HttpClientContext;
import org.apache.hc.client5.http.ssl.NoopHostnameVerifier;
import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory;
import org.apache.hc.core5.http.ClassicHttpRequest;
import org.apache.hc.core5.http.ContentType;
import org.apache.hc.core5.http.HttpEntity;
import org.apache.hc.core5.http.io.entity.EntityUtils;
import org.apache.hc.core5.http.io.entity.StringEntity;
import org.apache.hc.core5.ssl.SSLContexts;
import org.apache.hc.core5.ssl.TrustStrategy;
import org.apache.hc.core5.util.TimeValue;
import org.apache.hc.core5.util.Timeout;

import javax.net.ssl.SSLContext;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.util.Map;

/**
 * HTTP客户端工具类（支持HTTPS）
 * @author ThinkGem
 * @version 2017-3-27
 */
@Slf4j
public class HttpUtils {

	private HttpUtils() {
		throw new IllegalStateException("Utility class");
	}

	/** 默认超时时间：60秒 */
	private static final long REQUEST_TIMEOUT = 60000L;

	/**
	 * get请求
	 * @param url 接口地址
	 * @return
	 * @author wuhao
	 * @version 1.0.0 - 2023/3/25
	 */
	public static String get(String url) {
		return get(url, REQUEST_TIMEOUT);
	}

	/**
	 * get请求
	 * @param url 接口地址
	 * @param timeout 超时时间，单位：秒
	 * @return  
	 * @author wuhao
	 * @version 1.0.0 - 2023/3/25
	 */
	public static String get(String url, long timeout) {
		HttpGet httpGet = new HttpGet(url);
		RequestConfig requestConfig = RequestConfig.custom().setConnectionRequestTimeout(Timeout.ofMilliseconds(timeout)).build();
		httpGet.setConfig(requestConfig);
		return executeRequest(httpGet);
	}


	/**
	 * post请求
	 * @param url 请求地址
	 * @param jsonData json数据
	 * @return
	 * @author wuhao
	 * @version 1.0.0 - 2023/3/25
	 */
	public static String post(String url, JSONObject jsonData) {
		return post(url, jsonData, REQUEST_TIMEOUT);
	}

	/**
	 * post请求
	 * @param url 请求地址
	 * @param jsonData json数据
	 * @param timeout 超时时间，单位：秒
	 * @return
	 * @author wuhao
	 * @version 1.0.0 - 2023/3/25
	 */
	public static String post(String url, JSONObject jsonData, long timeout) {
		HttpPost httpPost = new HttpPost(url);
		RequestConfig requestConfig = RequestConfig.custom().setConnectionRequestTimeout(Timeout.ofMilliseconds(timeout)).build();
		httpPost.setConfig(requestConfig);
		// 请求数据
		StringEntity stringEntity = new StringEntity(jsonData.toString(), ContentType.APPLICATION_JSON, StandardCharsets.UTF_8.toString(), false);
		httpPost.setEntity(stringEntity);
		return executeRequest(httpPost);
	}

	/**
	 * post请求
	 * @param url 请求地址
	 * @param jsonData json数据
	 * @param headers 请求头
	 * @param timeout 超时时间，单位：秒
	 * @return
	 * @author wuhao
	 * @version 1.0.0 - 2023/3/25
	 */
	public static String post(String url, JSONObject jsonData, Map<String, Object> headers, long timeout) {
		HttpPost httpPost = new HttpPost(url);
		if(headers != null) {
			for (String key : headers.keySet()) {
				httpPost.setHeader(key, headers.get(key));
			}
		}
		RequestConfig requestConfig = RequestConfig.custom().setConnectionRequestTimeout(Timeout.ofMilliseconds(timeout)).build();
		httpPost.setConfig(requestConfig);
		// 请求数据
		StringEntity stringEntity = new StringEntity(jsonData.toString(), ContentType.APPLICATION_JSON, StandardCharsets.UTF_8.toString(), false);
		httpPost.setEntity(stringEntity);
		return executeRequest(httpPost);
	}

	/**
	 * post请求
	 * @param data 数据字符串，一般是加密的
	 * @return
	 * @author wuhao
	 * @version 1.0.0 - 2023/8/13
	 */
	public static String post(String url, String data) {
		HttpPost httpPost = new HttpPost(url);
		RequestConfig requestConfig = RequestConfig.custom().setConnectionRequestTimeout(Timeout.ofMilliseconds(60000)).build();
		httpPost.setConfig(requestConfig);
		// 请求数据
		StringEntity stringEntity = new StringEntity(data, StandardCharsets.UTF_8);
		httpPost.setEntity(stringEntity);
		return executeRequest(httpPost);
	}

	/**
	 * 上传文件
	 * @param url 上传地址
	 * @param file 二进制文件
	 * @param dataMap formData内容
	 * @return  
	 * @author wuhao
	 * @version 1.0.0 - 2023/3/25
	 */
	public static String upload(String url, File file, Map<String, String> dataMap) {
		HttpPost httpPost = new HttpPost(url);

		MultipartEntityBuilder reqEntityBuilder = MultipartEntityBuilder.create();
		if (dataMap != null){
			for (Map.Entry<String, String> entry : dataMap.entrySet()) {
				reqEntityBuilder.addPart(entry.getKey(),new StringBody(entry.getValue(), ContentType.create(
											 "text/plain", StandardCharsets.UTF_8)));
			}
		}
		// 把文件转成流对象FileBody
		FileBody bin = new FileBody(file);
		reqEntityBuilder.addPart("file", bin);
		httpPost.setEntity(reqEntityBuilder.build());

		return executeRequest(httpPost);
	}


	/**
	 * 下载文件
	 * @param url 文件地址
	 * @param file 本地文件
	 * @param timeout 超时，0 表示不设置超时时间
	 * @return
	 * @author wuhao
	 * @version 1.0.0 - 2023/4/27
	 */
	public static void download(String url, File file, long timeout) {
		int BUFFER_SIZE = 4096;
		InputStream in = null;
		FileOutputStream out = null;
		try {
			CloseableHttpClient httpClient = createHttpClient();
			HttpGet httpGet = new HttpGet(url);
			// 设置请求超时时间
			if (timeout > 0){
				RequestConfig requestConfig = RequestConfig.custom().setConnectionRequestTimeout(Timeout.ofMilliseconds(timeout)).build();
				httpGet.setConfig(requestConfig);
			}

			//执行get方法
			CloseableHttpResponse response = httpClient.execute(httpGet, HttpClientContext.create());

			// 获取响应实体
			HttpEntity entity = response.getEntity();
			if (entity != null) {
				try (InputStream inputStream = entity.getContent()) {

					// 创建文件输出流
					try (FileOutputStream outputStream = new FileOutputStream(file)) {
						// 缓冲区
						byte[] buffer = new byte[BUFFER_SIZE];
						int bytesRead;
						// 从输入流中读取数据，并写入到输出流中
						while ((bytesRead = inputStream.read(buffer)) != -1) {
							outputStream.write(buffer, 0, bytesRead);
						}
					}
				}
				// 消耗响应实体
				EntityUtils.consume(entity);
			}

		} catch (IOException e) {
			if (in != null) {
				try {
					in.close();
				} catch (IOException e1) {
					// TODO Auto-generated catch block
					e1.printStackTrace();
				}
			}
			if (out != null)
				try {
					out.close();
				} catch (IOException e1) {
					// TODO Auto-generated catch block
					e1.printStackTrace();
				}
			e.printStackTrace();
		}
		System.out.println("download, success!!");
	}


	/**
	 * 执行http请求
	 * @param httpRequest 对象
	 * @return  
	 * @author wuhao
	 * @version 1.0.0 - 2023/3/25
	 */
	public static String executeRequest(ClassicHttpRequest httpRequest) {
		String result = "";
		CloseableHttpClient httpClient = createHttpClient();
		CloseableHttpResponse response = null;
		try {
			response = httpClient.execute(httpRequest);

			// 获取响应对象
			HttpEntity resEntity = response.getEntity();
			if (resEntity != null) {
				// 打印响应长度
				log.debug("Response content length: " + resEntity.getContentLength());
				// 打印响应内容
				result= EntityUtils.toString(resEntity, StandardCharsets.UTF_8);
			}

			// 销毁
			EntityUtils.consume(resEntity);
		}catch ( ConnectTimeoutException ce) {
			result = "{\"resCode\":\"1\",\"resMsg\":\"接口超时\"}";
		}catch (Exception e) {
			result = "{\"resCode\":\"-1\",\"resMsg\":\"系统未知异常\"}";
			e.printStackTrace();
		}
		finally {
			try {
				if(response != null){
					response.close();
				}
				httpClient.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		return result;
	}

	/**
	 * 创建httpclient对象
	 * @return
	 * @author wuhao
	 * @version 1.0.0 - 2023/3/25
	 */
	public static CloseableHttpClient createHttpClient() {
		CloseableHttpClient httpClient;
		// 设置超时时间
		RequestConfig config = RequestConfig.custom()
				.setConnectionRequestTimeout(Timeout.ofMilliseconds(REQUEST_TIMEOUT))
				.setResponseTimeout(Timeout.ofMilliseconds(REQUEST_TIMEOUT)).build();
		// httpclient5经测试不设置支持ssl，也能调用https接口
		try {
			httpClient = HttpClients.custom()
					// 默认重试3次
					.setRetryStrategy(new DefaultHttpRequestRetryStrategy(3, TimeValue.ofSeconds(1)))
					.setDefaultRequestConfig(config)
					.setConnectionManager(getHttpClientConnectionManager()).build();
		} catch (KeyManagementException | NoSuchAlgorithmException | KeyStoreException e) {
			httpClient = HttpClients.createDefault();
			e.printStackTrace();
		}
		return httpClient;
	}

	private static HttpClientConnectionManager getHttpClientConnectionManager()
			throws NoSuchAlgorithmException, KeyStoreException, KeyManagementException {
		// 设置连接池最大连接数1000,最大并发数200，及支持ssl,tls
		return PoolingHttpClientConnectionManagerBuilder.create().setMaxConnTotal(1000).setMaxConnPerRoute(200)
				.setSSLSocketFactory(getSslConnectionSocketFactory()).build();
	}


	/**
	 * 支持SSL
	 *
	 * @return SSLConnectionSocketFactory
	 */
	private static SSLConnectionSocketFactory getSslConnectionSocketFactory()
			throws NoSuchAlgorithmException, KeyStoreException, KeyManagementException {
		TrustStrategy acceptingTrustStrategy = (x509Certificates, s) -> true;
		SSLContext sslContext = SSLContexts.custom().loadTrustMaterial(null, acceptingTrustStrategy).build();
		return new SSLConnectionSocketFactory(sslContext, new NoopHostnameVerifier());
	}

}
